package com.sfzd5.exam;

import org.opencv.core.CvType;
import org.opencv.core.Mat;
import org.opencv.core.Point;
import org.opencv.core.Rect;
import org.opencv.core.RotatedRect;
import org.opencv.core.Scalar;
import org.opencv.core.Size;
import org.opencv.imgproc.Imgproc;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class OpenCVHelper {

    /**
     * 二值化
     * @param src
     * @param blockSize
     * @param C
     * @return
     */
    public static Mat threshold(Mat src, double blockSize, int C){
        Mat dst = src.clone();
        int bs = (int) blockSize;
        if(bs%2==0)
            bs+=1;
        Imgproc.adaptiveThreshold(dst, dst, 255, Imgproc.ADAPTIVE_THRESH_MEAN_C, Imgproc.THRESH_BINARY, bs, C);
        return dst;
    }

    /**
     * 灰度
     * @param src
     * @return
     */
    public static Mat gray(Mat src){
        Mat dst = src.clone();
        Imgproc.cvtColor(src, dst, Imgproc.COLOR_RGB2GRAY);
        return dst;
    }

    /**
     * 画空心矩形
     * @param src
     * @param p1
     * @param p2
     * @param color
     */
    public static void drawRectangle(Mat src, Point p1, Point p2, Scalar color){
        Imgproc.rectangle(src, p1, p2, color);
    }

    /**
     * 画实心矩形
     * @param src
     * @param p1
     * @param p2
     * @param color
     */
    public static void fillRectangle(Mat src, Point p1, Point p2, Scalar color){
        Imgproc.rectangle(src, p1, p2, color, -1);
    }

    /**
     * 四边查找模板，切出四角模板中间的图片
     * @param src
     * @param template
     * @return
     */
    public static PageContentArea takePageContentArea(Mat src, Mat template){
        //模板宽度
        double templateWidth = template.cols();

        //分割成四块
        double srcWidth = src.cols();
        double srcHeight = src.rows();
        double blockWidth = srcWidth / 2;
        double blockHeight = srcHeight / 2;

        Rect rect1 = new Rect(0, 0, (int)blockWidth, (int)blockHeight);
        Mat m1  = new Mat(src, rect1);//左上
        Rect rect2 = new Rect((int)blockWidth, 0, (int)blockWidth, (int)blockHeight);
        Mat m2  = new Mat(src, rect2);//右上
        Rect rect3 = new Rect(0, (int)blockHeight, (int)blockWidth, (int)blockHeight);
        Mat m3  = new Mat(src, rect3);//左下
        Rect rect4 = new Rect((int)blockWidth, (int)blockHeight, (int)blockWidth, (int)blockHeight);
        Mat m4  = new Mat(src, rect4);//右下

        PageContentArea a = new PageContentArea();
        //模板匹配，找到方块
        a.p1 = TemplateMaching.process(m1, template);
        a.p2 = TemplateMaching.process(m2, template);
        a.p3 = TemplateMaching.process(m3, template);
        a.p4 = TemplateMaching.process(m4, template);

        //转换坐标到全图
        a.p1.x+=templateWidth;
        a.p1.y+=templateWidth;
        a.p2.y+=templateWidth;
        a.p2.x+=blockWidth;
        a.p3.x+=templateWidth;
        a.p3.y+=blockHeight;
        a.p4.x+=blockWidth;
        a.p4.y+=blockHeight;

        //计算新图宽度和高度
        a.newWidth = getDistance(a.p1, a.p2);
        a.newHeight = getDistance(a.p1, a.p3);
        return a;
    }

    public static Mat takePage(Mat src, PageContentArea p){
        return Perspective(src, p.p1, p.p2, p.p3, p.p4, p.newWidth, p.newHeight);
    }

    /**
     * 透视变换裁剪
     * @param yuan
     * @param p1
     * @param p2
     * @param p3
     * @param p4
     * @param nw
     * @param nh
     * @return
     */
    public static Mat Perspective(Mat yuan, Point p1, Point p2, Point p3, Point p4, double nw, double nh){
        //透视变换
        Mat src_mat=new Mat(4,1, CvType.CV_32FC2);
        Mat dst_mat=new Mat(4,1,CvType.CV_32FC2);

        //源4个点
        src_mat.put(0,0,
                p1.x, p1.y,
                p2.x, p2.y,
                p3.x, p3.y,
                p4.x, p4.y);

        //目标4个点
        dst_mat.put(0,0,
                0.0,0.0,
                nw,0.0,
                0.0,nh,
                nw, nh);
        Mat perspectiveTransform=Imgproc.getPerspectiveTransform(src_mat, dst_mat);
        Mat dst = new Mat((int)nw, (int)nh, yuan.type());
        Imgproc.warpPerspective(yuan, dst, perspectiveTransform, new Size(nw,nh));

        return dst;
    }

    /**
     * 旋转矩形
     * @param cannyMat  mat矩阵
     * @param rect   矩形
     * @return
     */
    public static Mat rotatedRectPerspective(Mat cannyMat, RotatedRect rect) {
        // 获取矩形的四个顶点
        Point[] rectPoint = new Point[4];
        rect.points(rectPoint);
        Point center = rect.center;

        Point p1 = null;
        Point p2 = null;
        Point p3 = null;
        Point p4 = null;
        for(Point p : rectPoint){
            if(p.x<center.x ){
                if( p.y < center.y) {
                    p1 = p;
                } else {
                    p3 = p;
                }
            } else {
                if( p.y < center.y) {
                    p2 = p;
                } else {
                    p4 = p;
                }
            }
        }

        //计算新图宽度和高度
        int nw = getDistance(p1, p2);
        int nh = getDistance(p1, p3);

        return Perspective(cannyMat, p1, p2, p3, p4, nw, nh);
    }

    /**
     * 计算两点间的距离
     * @param p1
     * @param p2
     * @return
     */
    private static int getDistance(Point p1, Point p2)
    {
        double juli = Math.sqrt((p1.x - p2.x)* (p1.x - p2.x)+(p1.y - p2.y)* (p1.y - p2.y));
        return (int)juli;
    }

    public static int countBlack(Mat mat){
        int total = 0;
        for(int col = 0; col< mat.cols(); col++){
            for(int row=0; row<mat.rows(); row++){
                if (mat.get(row, col)[0] == 0) total++;
            }
        }
        return total;
    }

    /**
     * 切出选项
     * @param curMat
     * @return
     */
    public static List<List<Mat>> cutOption(Mat curMat) {
        //纵向查找黑点数
        List<Point> whihtPoints = histc(curMat);
        //排序，找到白色区域段
        Collections.sort(whihtPoints, new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                return Double.compare(o2.y, o1.y);
            }
        });
        //最大的四个白色区域段
        List<Point>  whihtPoints4 = new ArrayList<>();
        whihtPoints4.add(whihtPoints.get(0));
        whihtPoints4.add(whihtPoints.get(1));
        whihtPoints4.add(whihtPoints.get(2));
        whihtPoints4.add(whihtPoints.get(3));

        /*
         * 每题宽度=最宽的四个空白之间的距离的平均数
         * 比例：每题宽度 / 114
         * 每项宽度：比例 * 29
         * 左移间隔：比例 * 5
         */
        Collections.sort(whihtPoints4, new Comparator<Point>() {
            @Override
            public int compare(Point o1, Point o2) {
                return Double.compare(o1.x, o2.x);
            }
        });

        //计算区域、选项，等宽度
        double qw = ((whihtPoints4.get(1).x - whihtPoints4.get(0).x) +(whihtPoints4.get(2).x - whihtPoints4.get(1).x) +(whihtPoints4.get(3).x - whihtPoints4.get(2).x))/3;
        double bl = qw / 183;
        double xxw = bl * 29;
        double jgw = bl * 5;

        Mat cc = curMat.clone();
        int h = curMat.rows();

        List<List<Mat>> result = new ArrayList<>();
        for(int i=0; i< whihtPoints4.size(); i++){
            double x = whihtPoints4.get(i).x;
            x = x - xxw * 4 + jgw;
            List<Mat> questionOptions = new ArrayList<>();
            for(int p =0; p<4; p++) {
                int px = (int)(x + p * xxw);
                Rect rect = new Rect(px, 0, (int)xxw, h);
                Mat m = new Mat(cc, rect);
                questionOptions.add(m);
            }
            result.add(questionOptions);
        }

        return result;
    }

    /**
     * 分割每个选项宽度
     * 分割比例：
     * 每题宽度=最宽的四个空白之间的距离的平均数
     * 比例：每题宽度 / 114
     * 每项宽度：比例 * 29
     * 左移间隔：比例 * 5
     * @return
     */
    private static List<Point> histc(Mat curMat)
    {
        List<Point> whihtPoints = new ArrayList<>();
        int startIndex = 0;//记录进入字符区的索引
        int endIndex = 0;//记录进入空白区域的索引
        boolean inBlock = false;//是否遍历到了字符区内
        int total = 0;
        for (int col = 0; col < curMat.cols(); col++)//遍历每个像素点
        {
            total = 0;
            for (int row = 0; row < curMat.rows(); row++) {
                if (curMat.get(row, col)[0] == 0) total++;
            }

            if (col == 0) {
                inBlock = total > 0;
            }

            if (!inBlock && total > 0) { //开始进入黑色区域
                inBlock = true;
                endIndex = col;
                Point p = new Point(startIndex, endIndex - startIndex);
                whihtPoints.add(p);
            }
            if (inBlock && total == 0) {//进入空白区
                startIndex = col;
                inBlock = false;
            }
        }
        return whihtPoints;
    }


    /**
     * 图像切割,水平投影法切割
     * @param curMat
     * @return
     */
    public static List<Mat> cutImgX(Mat curMat) {
        Mat srcImg = curMat.clone();
        double perPixelValue = 0;
        int width = srcImg.cols();
        int height = srcImg.rows();
        int projectValArry[] = new int[height];
        for(int i=0; i<projectValArry.length; i++) projectValArry[i] = 0;
        double total = 0;
        for (int col = 0; col < height; col++)//遍历每个像素点
        {
            for (int row = 0; row < width; row++)
            {
                perPixelValue = srcImg.get(col, row)[0];
                if (perPixelValue == 0)//如果是白底黑字
                {
                    projectValArry[col]++;
                    total++;
                }
            }
        }

        double sp = total / srcImg.rows() / 2;

        List<Mat> roiList = new ArrayList<>();//用于储存分割出来的每个字符
        int startIndex = 0;//记录进入字符区的索引
        int endIndex = 0;//记录进入空白区域的索引
        boolean inBlock = false;//是否遍历到了字符区内

        for (int i = 0; i <srcImg.rows(); i++)
        {
            if (!inBlock && projectValArry[i] > sp)//进入字符区
            {
                inBlock = true;
                startIndex = i;
            }
            else if (inBlock && projectValArry[i] <= sp)//进入空白区
            {
                endIndex = i;
                inBlock = false;

                Rect rect1 = new Rect(0, startIndex, width, endIndex-startIndex + 1);
                Mat m1  = new Mat(srcImg, rect1);//左上

                roiList.add(m1);
            }
        }

        return roiList;
    }


    /**
     * 图像腐蚀 膨胀
     * @param curMat
     * @param d 腐蚀的单位越小，图片越接近原图
     * @return
     */
    public static Mat erodeImg(Mat curMat, int d) {
        Mat mat = curMat.clone();
        Mat outImage = new Mat();

        // size 越小，腐蚀的单位越小，图片越接近原图
        Mat structImage = Imgproc.getStructuringElement(Imgproc.MORPH_RECT, new Size(d, d));

        /**
         * 图像腐蚀
         * 腐蚀说明： 图像的一部分区域与指定的核进行卷积，
         * 求核的最`小`值并赋值给指定区域。
         * 腐蚀可以理解为图像中`高亮区域`的'领域缩小'。
         * 意思是高亮部分会被不是高亮部分的像素侵蚀掉，使高亮部分越来越少。
         */
        Imgproc.erode(mat, outImage, structImage, new Point(-1, -1), 2);
        mat = outImage;

        /**
         * 膨胀
         * 膨胀说明： 图像的一部分区域与指定的核进行卷积，
         * 求核的最`大`值并赋值给指定区域。
         * 膨胀可以理解为图像中`高亮区域`的'领域扩大'。
         * 意思是高亮部分会侵蚀不是高亮的部分，使高亮部分越来越多。
         */
        Imgproc.dilate(mat, outImage, structImage , new Point(-1, -1), 2);
        mat = outImage;

        return mat;
    }
}
